package org.apache.mina.protocol.dns;

import java.net.InetAddress;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Implements a cache for DNS results.
 * 
 * @author The Apache MINA Project (dev@mina.apache.org)
 * @version $Rev: 84 $, $Date: 2008-02-22 16:33:48 +0000 (Fri, 22 Feb 2008) $
 */
class DnsCache {

    private static final int MIN_TTL = 3600;

    public static final int MAX_TTL = 86000;

    private Map<String, List<InetAddress>> dnsCache = new ConcurrentHashMap<String, List<InetAddress>>();

    private CacheCleaner cacheCleaner = new CacheCleaner();

    /**
     * Search for a host name in the DNS cache.
     * 
     * 
     * @param domainName the searched name
     * 
     * @return a {@link List<InetAddress>} with the associated IP addresses or 
     *         null if the entry is not found in cache.
     */
    public List<InetAddress> get(String domainName) {
        return dnsCache.get(domainName);
    }

    public void put(String domainName, List<InetAddress> addresses, int ttl) {

        dnsCache.put(domainName, addresses);

        // Add the results to the expiration map
        long now = System.currentTimeMillis() / 1000;
        long expires = now + Math.max(MIN_TTL, ttl);

        cacheCleaner.add(expires, domainName);

        if (log.isDebugEnabled()) {

            String msg = String.format("Added entry: %s -> %s (ttl=%d)",
                    domainName, addresses, ttl);
            log.debug(msg);
        }
    }

    private static class ExpireEntry implements Comparable<ExpireEntry> {
        final long expireTime;

        final String domainName;

        public ExpireEntry(long expireTime, String domainName) {
            this.expireTime = expireTime;
            this.domainName = domainName;
        }

        /* (non-Javadoc)
         * @see java.lang.Comparable#compareTo(java.lang.Object)
         */
        public int compareTo(ExpireEntry o) {
            if (expireTime == o.expireTime)
                return 0;
            return expireTime < o.expireTime ? -1 : 1;
        }

    }

    private class CacheCleaner extends Thread {

        private PriorityQueue<ExpireEntry> expireQueue = new PriorityQueue<ExpireEntry>();

        public CacheCleaner() {
            start();
        }

        void add(long expires, String domainName) {
            synchronized (expireQueue) {
                expireQueue.add(new ExpireEntry(expires, domainName));
            }
        }

        /* (non-Javadoc)
         * @see java.lang.Thread#run()
         */
        @Override
        public void run() {
            while (isAlive()) {

                try {
                    sleep(5000);
                } catch (InterruptedException e) {
                    continue;
                }

                long now = System.currentTimeMillis() / 1000;

                synchronized (expireQueue) {
                    while (true) {
                        ExpireEntry entry = expireQueue.peek();
                        if (entry == null || entry.expireTime > now) {
                            break;
                        }

                        log.debug("Removed entry: {}", entry.domainName);
                        dnsCache.remove(entry.domainName);
                        expireQueue.poll();
                    }
                }
            }
        }

    }

    private static Logger log = LoggerFactory.getLogger(DnsCache.class);
}
